package com.corticon.dialogs.validation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

import org.springframework.validation.Errors;

/**
 *
 * @author Yanglifan
 */
public class GenericHibernateValidator<T> extends GenericValidator {

    private ClassValidator<T> hibernateClassValidator = new ClassValidator<T>(super.
        getValidateObjectClass());
    @Resource
    protected ValidationMessageFormatter validationMessageFormatter;

    @Override
    public void validate(Object object, Errors errors) {
        hibernateValidate(object, errors);
        complexValidate(object, errors);
    }

    /**
     * This method invoke the true validation logic, then convert the format of the error messages
     * into Map type. Support i18n.
     * @param object The object be validated.
     * @param errors
     * @param locale Location info, used for i18n.
     * @return 
     */
    public Map<String, List<String>> invokeValidate(Object object, Errors errors, Locale locale) {
        Map<String, List<String>> errorMessages = new HashMap<String, List<String>>();

        // Invoke the true validate method.
        validate(object, errors);
        // Convert the error messages from org.springframework.validation.Errors to Map.
        errorMessages = validationMessageFormatter.convertErrorsMessageToMap(object, errors, locale);
        return errorMessages;
    }

    /**
     * This method use {@link ClassValidator} to validate a model with hibernate validator
     * annotations. And reject the error messages into {@link Errors}.
     * 
     * @param object: The object should be validated.
     * @param errors: The errors message.
     */
    private void hibernateValidate(Object object, Errors errors) {
        T hibernateValidateObject = (T) object;
        InvalidValue[] validateMessages = null;
        validateMessages = hibernateClassValidator.getInvalidValues(
            hibernateValidateObject);

        if (validateMessages != null && validateMessages.length > 0) {
            for (InvalidValue invalidValue : validateMessages) {
                errors.rejectValue(invalidValue.getPropertyPath(), invalidValue.getMessage(),
                    "default message");
            }
        }
    }

    /**
     * The template method you should implement. Hibernate Validator can
     * complete the most of validation works. But some complex validations
     * should be implemented programmingly.
     *
     * @param object
     * @param errors
     */
    protected void complexValidate(Object object, Errors errors) {
    }

    /**
     * The method is used to validate the simple string like a name. The numbers and alphabets and
     * '_' are valid.
     *
     * @param errors
     * @param validateString
     * @param field
     * @param messageCode
     */
    protected void simpleStringValidate(Errors errors, String validateString, String field,
        String messageCode) {
        List<Character> excludedChars = new ArrayList<Character>();
        excludedChars.add('_');
        commonCharactersValidate(validateString, excludedChars, errors, field, messageCode, null,
            "default message");
    }

    protected void commonCharactersValidate(String characters, List<Character> excludedChars,
        Errors errors, String fieldName, String errorCode, Object[] errorArgs, String defaultMessage) {

        boolean isValid = true;

        if (isValid) {
            isValid =
                checkInvalidCharacters(characters, ' ', '/', excludedChars) && checkInvalidCharacters(
                characters, ':', '@', excludedChars) && checkInvalidCharacters(characters, '[', '`',
                excludedChars) && checkInvalidCharacters(characters, '{', '~', excludedChars);
        }

        // If the password is invalid, add the error message.
        if (!isValid) {
            errors.rejectValue(fieldName, errorCode, errorArgs, defaultMessage);
        }
    }

    protected boolean checkValidCharacters(String value, char asciiBegin, char asciiEnd,
        List<Character> excludedChars) {
        boolean isValidPassword = false;

        int i = asciiBegin;

        while (!isValidPassword) {

            if (excludedChars != null && excludedChars.contains((char) i)) {
                i++;
                continue;
            }

            isValidPassword = value.indexOf((char) i) > -1 ? true : false;

            i++;
            if (i > asciiEnd) {
                break;
            }
        }

        return isValidPassword;
    }

    protected boolean checkInvalidCharacters(String value, char asciiBegin, char asciiEnd,
        List<Character> excludedChars) {
        boolean isValidPassword = true;

        int i = asciiBegin;

        while (isValidPassword) {

            if (excludedChars != null && excludedChars.contains((char) i)) {
                i++;
                continue;
            }

            isValidPassword = value.indexOf((char) i) > -1 ? false : true;

            i++;
            if (i > asciiEnd) {
                break;
            }
        }

        return isValidPassword;
    }

    public void setValidationMessageFormatter(ValidationMessageFormatter validationMessageFormatter) {
        this.validationMessageFormatter = validationMessageFormatter;
    }
}
